package Timer;

import javafx.scene.layout.Priority;

import java.util.concurrent.PriorityBlockingQueue;
class Go {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() ->{
            System.out.println("执行任务1");
        },1000);
        myTimer.schedule(() ->{
            System.out.println("执行任务2");
        },2000);
        myTimer.schedule(() ->{
            System.out.println("执行任务3");
        },3000);
    }
}
class Task {
    public Runnable runnable;    //任务的内容
    public long time;            //任务执行时间

    public Task(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
}
public class MyTimer {
    /**  实现一个计时器  1. 使用一个扫描线程,负责判定时间到/执行任务
     *                2. 使用一个优先级队列,来保存所有被注册的任务
     *    使用优先级队列得原因 : 因为需要根据时间来执行任务 , 而时间在前面的任务需要先执行 ,所以使用一个小根堆,确保堆顶的元素是需要先执行的
     */
    private Thread t;
    public MyTimer() {
        t = new Thread(() -> {
            while (true) {
                try {
                    //在此处将锁的位置放大,就可以保证,当notify()的时候,wait已经执行完了
                    //否则就会出现,notify通知没产生效果,wait的时间仍然是之前的时间 要比堆顶任务的执行时间要长 ,错过执行这个任务
                    synchronized (this) {
                        Task task = priorityBlockingQueue.take();
                        if (System.currentTimeMillis() < task.time) {
                            //取出队首元素,判断是否到时间了
                            //时间没到,就把任务放回去,到了就执行任务
                            //这个时候会出现"盲等",比如任务执行时间距离现在时间相差很多,那么cpu就会反复做取出元素,再放回去的操作,浪费cpu资源
                            priorityBlockingQueue.put(task);
                            /**  不能使用sleep的原因,如果又有了一个新的任务,而且执行时间在队首元素执行之前,那么就会出现问题
                             *   Thread.sleep(task.time - System.currentTimeMillis());
                             *   这里使用带参数的wait()原因 : 当一直不传入新任务的时候,到了原来队首元素执行的时间,就结束等待,否则就会死等
                             */
                            this.wait(task.time - System.currentTimeMillis());
                        } else {
                            task.runnable.run();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
    //使用一个优先级阻塞队列来保存任务 (因为涉及到修改操作,所以会出现线程安全问题 ,而PriorityBlockingQueue 本身就是线程安全的)
    public PriorityBlockingQueue<Task> priorityBlockingQueue = new PriorityBlockingQueue<>(10,(o1,o2) -> {return (int)(o1.time - o2.time);});
    //指定两个参数 : 1.任务 内容  2. 任务在多少时间之后进行,需要先获取本身的时间
    public void schedule(Runnable runnable,long after) {
        Task task = new Task(runnable,System.currentTimeMillis() + after);
        priorityBlockingQueue.put(task);
        synchronized (this) {
            this.notify();
        }
    }
}
